package com.xyy.saas.payment.service.job.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.pingan.openbank.api.sdk.client.ApiClient;
import com.pingan.openbank.api.sdk.common.http.HttpResult;
import com.pingan.openbank.api.sdk.entity.FileRequest;
import com.pingan.openbank.api.sdk.entity.SdkRequest;
import com.pingan.openbank.api.sdk.exception.OpenBankFileNotFoundException;
import com.pingan.openbank.api.sdk.exception.OpenBankSdkException;
import com.pingan.openbank.api.sdk.exception.OpenBankSdkRunTimeException;
import com.sdb.util.Base64;
import com.sdb.util.SignUtil;
import com.xyy.saas.payment.common.constants.ApolloConstants;
import com.xyy.saas.payment.cores.enums.FbankTradeCodeEnum;
import com.xyy.saas.payment.dao.mapper.PrePayOrderMapper;
import com.xyy.saas.payment.dao.mapper.RefundOrderMapper;
import com.xyy.saas.payment.dao.model.*;
import com.xyy.saas.payment.enums.PinganMethod;
import com.xyy.saas.payment.exception.PaymentException;
import com.xyy.saas.payment.manage.cores.enums.ResultCodeEnum;
import com.xyy.saas.payment.pojo.dto.*;
import com.xyy.saas.payment.pojo.vo.FBankPayRecordsVo;
import com.xyy.saas.payment.pojo.vo.QueryChannelClearVo;
import com.xyy.saas.payment.service.*;
import com.xyy.saas.payment.trace.AlarmService;
import com.xyy.saas.payment.trace.DingTalkMessage;
import com.xyy.saas.payment.util.*;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import util.AESUtil;

import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 平安交易对账、账户对账
 */
@Slf4j
@Component
@DependsOn({"apolloUtil"})
public class PinganTradeCheckBillService {

    private static ApiClient apiClient = ApiClient.getInstance(ApolloUtil.getProperty("pingan.configPath"));
    @Resource
    private FbankPayRecordsService fbankPayRecordsService;
    @Resource
    private PayResultOrderService payResultOrderService;
    @Resource
    private PrePayOrderMapper prePayOrderMapper;
    @Resource
    private RefundOrderMapper refundOrderMapper;
    @Resource
    private SeparateOrderService separateOrderService;
    @Resource
    private PayAccountService payAccountService;
    @Resource
    private PayReportService payReportService;
    @Resource
    private UserInfoService userInfoService;
    @Autowired
    private LeafUtil leafUtil;
    @Resource
    private AlarmService alarmService;

    public void download(Date fileDate, boolean reimport) {
        // 交易
        download(QueryReconciliationFileDto.builder().fileType("JY").fileDate(fileDate).build(), reimport);
        // 充值
        download(QueryReconciliationFileDto.builder().fileType("CZ").fileDate(fileDate).build(), reimport);
        // 提现
        download(QueryReconciliationFileDto.builder().fileType("TX").fileDate(fileDate).build(), reimport);
        // 分账
        download(QueryReconciliationFileDto.builder().fileType("FZ").fileDate(fileDate).build(), reimport);
        // 余额
        download(QueryReconciliationFileDto.builder().fileType("YE").fileDate(fileDate).build(), reimport);
        downloadDailyClearDataByFileDate(fileDate, reimport);
    }

    private static final String PREFIX = "PinganTradeCheckBillService";

    public void download(QueryReconciliationFileDto param, boolean reimport) {
        String logPrefix = PREFIX + "-" + param.getFileType() + ":";
        log.info("{}PinganTradeCheckBillService.download fileType={}, param={}", logPrefix, param.getFileType(), JSON.toJSONString(param));
        if (StringUtils.isBlank(param.getFileType())) {
            return;
        }
        if (param.getFileDate() == null) {
            return;
        }

        try {
            // 1.组装请求参数
            JSONObject requestParam = new JSONObject();
            requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
            requestParam.put("FileType", param.getFileType());
            requestParam.put("FileDate", DateUtil.format(param.getFileDate(), DateUtil.DATEFORMATDAY_2));

            // 2.发送请求
            PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6103);

            // 3.解析参数
            QueryReconciliationFileVo fileVo = QueryReconciliationFileVo.builder().
                    resultNum(result.getResultString("ResultNum")).
                    fileItems(QueryReconciliationFileVo.parseItems(result.getResultList("TranItemArray"))).
                    build();

            // 下载
            long start = System.currentTimeMillis();
            List<String> realFilePathList = downloadFile(param, fileVo);
            log.info("{} 下载文件,fileType={}完成，耗时{}ms", logPrefix, param.getFileType(), System.currentTimeMillis() - start);

            if(realFilePathList == null) {
                log.info("{} PinganTradeCheckBillService.download error, param={}", logPrefix, JSON.toJSONString(param));
                return;
            }

            // 解析导入数据库
            for(String realFilePath : realFilePathList) {
                start = System.currentTimeMillis();
                read(logPrefix, realFilePath, param, reimport);
                log.info("{} 解析文件,fileTyp={}, realFilePath={}完成，耗时{}ms", logPrefix, param.getFileType(), realFilePath, System.currentTimeMillis() - start);
            }

            log.info("{} PinganTradeCheckBillService.download success, param={}", logPrefix, JSON.toJSONString(param));
        }
        catch(Exception e) {
            log.error("{} PinganTradeCheckBillService.download error, param={}", logPrefix, JSON.toJSONString(param), e);
            alarmService.alarm(DingTalkMessage.builder()
                    .type("平安对账文件处理异常")
                    .method("PinganTradeCheckBillService#download")
                    .msg("错误信息：" + e.getMessage())
                    .param("param:" + JSON.toJSONString(param))
                    .build());
        }
    }

    /**
     * 文件下载到本机，返回多个文件路径
     */
    private List<String> downloadFile(QueryReconciliationFileDto param, QueryReconciliationFileVo result) {
        log.info("PinganTradeCheckBillService#downloadFile result={}", JSONUtils.toJSON(result));
        List<QueryReconciliationFileItem> fileItems = result.getFileItems();

        if(org.springframework.util.CollectionUtils.isEmpty(fileItems)) {
            return null;
        }

        String downloadFilePath = "/tmp/pingan_trade_check_bill_download/" + param.getFileType() + "/" + DateUtil.format(param.getFileDate(), DateUtil.DATEFORMATDAY_2) + "/";

        List<String> realFilePathList = new ArrayList<>();

        try {
            for (QueryReconciliationFileItem item : fileItems) {
                // 1.组装参数
                FileRequest fileRequest = new FileRequest();
                fileRequest.setFileNo(item.getFilePath());
                fileRequest.setRandomPwd(item.getRandomPassword());
                PicUtil.createFilePath(downloadFilePath);
                fileRequest.setFilePathName(downloadFilePath + item.getFileName());

                // 2.发送请求下载文件
                apiClient.fileDownLoad(fileRequest);

                String realFileName = item.getFileName().substring(NumberUtils.INTEGER_ZERO, item.getFileName().lastIndexOf("."));
                String srcFilePath = downloadFilePath + realFileName + ".enc";
                String zipFilePath = downloadFilePath + realFileName + ".zip";
                byte[] bKey = Base64.decode(item.getRandomPassword().getBytes());

                // 3.解密
                SignUtil.decrypt(srcFilePath, zipFilePath, bKey, SignUtil.ALG_DES, SignUtil.KS_DES, null);

                String realFilePath = downloadFilePath + realFileName;

                // 4.解压
                SignUtil.uncompress(zipFilePath, realFilePath);

                if(StringUtils.endsWith(realFilePath, ".txt")) {
                    realFilePathList.add(realFilePath);
                }
            }
            log.info("PinganTradeCheckBillService#downloadFile success realFilePathList={}", realFilePathList);
            return realFilePathList;
        }
        catch(Exception ex) {
            log.error("PinganTradeCheckBillService#downloadFile error" ,ex);
            alarmService.alarm(DingTalkMessage.builder()
                    .type("平安对账文件下载异常")
                    .method("PinganTradeCheckBillService#downloadFile")
                    .msg("错误信息：" + ex.getMessage())
                    .param("param:" + JSON.toJSONString(param))
                    .build());
            return null;
        }
    }

    /**
     * 读取对账文件并写入数据库交易对账表
     */
    private void read(String logPrefix, String filePath, QueryReconciliationFileDto param, boolean reimport) {
        InputStream in = null;
        BufferedReader br = null;
        int size = 0;
        try {
            in = new FileInputStream(filePath);
            if(in != null) {
                size++;
                br = new BufferedReader(new InputStreamReader(in, "GB18030"));
                String data = null;
                Set<String> tradeTypeSet = new HashSet<>();
                while ((data = br.readLine()) != null) {
                    FbankPayRecords fbankPayRecords = null;
                    try {
                        if (Boolean.parseBoolean(ApolloUtil.getProperty("wx_pay_records_download_debug", "false"))) {
                            log.info("{} 原始数据：{}", logPrefix, data);
                        }

                        if (param.getFileType().equals("YE")) {
                            parseAndSavePayReportByPinganCheckBillYE(param.getFileDate(), data);
                            continue;
                        }
                        long start = System.currentTimeMillis();
                        if (param.getFileType().equals("JY")) {
                            fbankPayRecords = FBankPayRecordsVo.parsePinganCheckBillJY(data);
                        } else if (param.getFileType().equals("CZ")) {
                            fbankPayRecords = FBankPayRecordsVo.parsePinganCheckBillCZ(data);
                        } else if (param.getFileType().equals("TX")) {
                            fbankPayRecords = FBankPayRecordsVo.parsePinganCheckBillTX(data);
                        } else if (param.getFileType().equals("FZ")) {
                            fbankPayRecords = FBankPayRecordsVo.parsePinganCheckBillFZ(data);
                        }

                        // parse耗时
                        long parseTime = System.currentTimeMillis() - start;

                        if (fbankPayRecords == null) {
                            log.info("{} PinganTradeCheckBillService#read data parse fbankPayRecords is null, data={}, filePath={}, param={}", logPrefix, data, filePath, param);
                            continue;
                        }
                        if (StringUtils.isBlank(fbankPayRecords.getClientSerialNo()) || StringUtils.isBlank(fbankPayRecords.getOrderNo())) {
                            log.error("{} PinganTradeCheckBillService#read parse fbankPayRecords clientSerialNo or orderNo is null, fbankPayRecords={}", logPrefix, JSON.toJSONString(fbankPayRecords));
                            continue;
                        }
                        if (fbankPayRecords.getTradeType() != null) {
                            tradeTypeSet.add(fbankPayRecords.getTradeType());

                            // 填充tradeCode
                            try {
                                fillFbankTradeCode(fbankPayRecords);
                            } catch (Exception e) {
                                log.error("{} PinganTradeCheckBillService fillFbankTradeCode erorr, clientSerialNo={}", logPrefix, fbankPayRecords.getClientSerialNo(), e);
                            }

                        }

                        start = System.currentTimeMillis();
                        // 校验交易记录是否存在
                        FbankPayRecords old = fbankPayRecordsService.queryByOrderNo(fbankPayRecords.getOrderNo());
                        if (old != null) {
                            if (reimport) {
                                fbankPayRecordsService.deleteById(old.getId());
                            } else {
                                log.info("{} PinganTradeCheckBillService#read fbankPayRecordsService.insertSelective is exist, recordsVo.getOrderNo={}", logPrefix, fbankPayRecords.getOrderNo());
                                continue;
                            }
                        }
                        // 校验交易订单是否存在耗时
                        long checkExists = System.currentTimeMillis() - start;

                        start = System.currentTimeMillis();
                        fillParams(fbankPayRecords, logPrefix);
                        // 填充数据耗时
                        long fillTime = System.currentTimeMillis() - start;

                        if (Boolean.parseBoolean(ApolloUtil.getProperty("wx_pay_records_download_debug", "false"))) {
                            log.info("{} 入库数据：{}", logPrefix, JSONUtils.toJSON(fbankPayRecords));
                        }

                        start = System.currentTimeMillis();
                        fbankPayRecordsService.insertSelective(fbankPayRecords);
                        // 添加数据耗时
                        long addTime = System.currentTimeMillis() - start;

                        log.info("{} PinganTradeCheckBillService#read fbankPayRecordsService.insertSelective success reimport={}, parse耗时:{}ms, 校验订单是否存在耗时:{}ms, 填充数据耗时:{}, 添加数据耗时:{}ms, 已处理数据量:{}", logPrefix, reimport, parseTime, checkExists, fillTime, addTime, size);

                    } catch (Exception e1) {
                        log.error(logPrefix + "PinganTradeCheckBillService#read error, data=" + data + "   fbankPayRecords=" + JSON.toJSONString(fbankPayRecords), e1);
                        alarmService.alarm(DingTalkMessage.builder()
                                .type("平安对账数据读取异常")
                                .method("PinganTradeCheckBillService#read")
                                .msg("错误信息：" + e1.getMessage())
                                .param("clientSerialNo: " + (fbankPayRecords != null ? fbankPayRecords.getClientSerialNo() : null))
                                .build());
                    }
                }
                log.info("{} PinganTradeCheckBillService#read tradeTypeSet={}", logPrefix, tradeTypeSet);
            }
        }
        catch(Exception ex) {
            log.error("{} PinganTradeCheckBillService#read error", logPrefix, ex);
            alarmService.alarm(DingTalkMessage.builder()
                    .type("平安对账文件解析异常")
                    .method("PinganTradeCheckBillService#read")
                    .msg("错误信息：" + ex.getMessage())
                    .param("param:" + JSON.toJSONString(param))
                    .build());
        }
        finally {
            if(br != null) {
                try {
                    br.close();
                }
                catch(Exception ex) {
                    log.error(ex.getMessage(), ex);
                }
            }
            if(in != null) {
                try {
                    in.close();
                }
                catch(Exception ex) {
                    log.error(ex.getMessage(), ex);
                }
            }
        }
    }

    private void fillFbankTradeCode(FbankPayRecords fbankPayRecords) {
        Integer tradeCode = FbankTradeCodeEnum.maps.get(fbankPayRecords.getTradeType());
        if (tradeCode != null) {
            fbankPayRecords.setTradeCode(tradeCode);
        }
    }

    /**
     * 冻结充值撤销、在途充值撤销 businessNo填充refundNo
     */
    private void refreshFbankRechargeRefundBizNo(FbankPayRecords fbankPayRecords, String logPrefix) {
        if (!Lists.newArrayList(FbankTradeCodeEnum.FTC_3.getCode(), FbankTradeCodeEnum.FTC_5.getCode()).contains(fbankPayRecords.getTradeCode())) {
            return;
        }

        if (StrUtil.isBlank(fbankPayRecords.getBusinessNo())) {
            log.error("{} refreshFbankRechargeRefundBizNo businessNo is blank, clientSerialNo:{}", logPrefix, fbankPayRecords.getClientSerialNo());
            return;
        }

        try {
            final String businessNo = fbankPayRecords.getBusinessNo();
            String targetBusinessNo = null;

            // 一个支付单包含多次部分退款
            List<RefundOrder> refundOrders = refundOrderMapper.selectListByPayNo(businessNo);
            if (CollectionUtils.isEmpty(refundOrders)) {
                log.error("{} refreshFbankRechargeRefundBizNo refundOrders is empty. clientSerialNo:{}, businessNo:{}", logPrefix, fbankPayRecords.getClientSerialNo(), businessNo);
                return;
            }

            int fbankAbsFen = AmountCastUtil.convertYuanAbsToFen(fbankPayRecords.getAmount());
            List<RefundOrder> confirmRefundOrders = refundOrders.stream()
                    .filter(r -> r.getAmount() == fbankAbsFen)
                    .collect(Collectors.toList());
            if (CollectionUtils.isEmpty(confirmRefundOrders)) {
                log.error("{} refreshFbankRechargeRefundBizNo same amount is empty. businessNo:{}, refundOrders:{}", logPrefix, businessNo, JSON.toJSONString(refundOrders));
                return;
            }

            // 多次部分退款金额相同的情况 反查确认
            for (RefundOrder confirmRefundOrder : confirmRefundOrders) {
                String refundNo = confirmRefundOrder.getRefundNo();
                FbankPayRecords existFbankPayRecords = fbankPayRecordsService.queryOneByBusinessNoAndTradeCodes(refundNo,
                        Lists.newArrayList(FbankTradeCodeEnum.FTC_3.getCode(), FbankTradeCodeEnum.FTC_5.getCode()));
                if (existFbankPayRecords == null) {
                    targetBusinessNo = refundNo;
                    fbankPayRecords.setBusinessNo(refundNo);
                    break;
                }
            }

            if (targetBusinessNo == null) {
                log.error("{} refreshFbankRechargeRefundBizNo##fbankPayRecords-businessNo无匹配的退款单记录，clientSerialNo:{}", logPrefix, fbankPayRecords.getClientSerialNo());
            }

        } catch (Exception e) {
            log.error("{} refreshFbankRechargeRefundBizNo error, clientSerialNo:{}", logPrefix, fbankPayRecords.getClientSerialNo(), e);
        }
    }

    /**
     * 交易对账
     */
    private void fillParams(FbankPayRecords fbankPayRecords, String logPrefix) {
        if("登记挂账".equals(fbankPayRecords.getTradeType())) {
            fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo());
            SeparateOrder separateOrder = separateOrderService.querySeparateOrderBySeparateNo(fbankPayRecords.getClientSerialNo());
            if(separateOrder == null) {
                return;
            }
            fbankPayRecords.setBusinessType("ec");
            fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getOutAccountNo(), fbankPayRecords.getOutBankCode()));

            String businessUserId = payAccountService.getBusinessUserIdByAccountId(fbankPayRecords.getInAccountNo());
            if (separateOrder.getBusinessOrderNo().contains("SH")) {
                fbankPayRecords.setExtra(getExtraByBusinessUserIdAndSeparateOrder(businessUserId, separateOrder));
                fbankPayRecords.setRemarks("平安结算-POP余额迁移" + (StringUtils.isBlank(fbankPayRecords.getRemarks()) ? "" : "(" + fbankPayRecords.getRemarks() + ")"));
            } else if (separateOrder.getBusinessOrderNo().toLowerCase().contains("manage") ||
                    (fbankPayRecords.getRemarks() != null && fbankPayRecords.getRemarks().contains("manage"))) {
                fbankPayRecords.setExtra(getExtraByBusinessUserIdAndSeparateOrder(businessUserId, separateOrder));
                fbankPayRecords.setRemarks("平安管理后台-转账" + (StringUtils.isBlank(fbankPayRecords.getRemarks()) ? "" : "(" + fbankPayRecords.getRemarks() + ")"));
            } else {
                fbankPayRecords.setExtra(getExtraByBusinessUserIdAndPayNo(businessUserId, separateOrder.getBusinessOrderNo()));
                fbankPayRecords.setRemarks("平安结算-富民支付、富民降级到直连支付" + (StringUtils.isBlank(fbankPayRecords.getRemarks()) ? "" : "(" + fbankPayRecords.getRemarks() + ")"));
            }

        } else if("见证+收单资金冻结".equals(fbankPayRecords.getTradeType())) {
            fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo());
            fbankPayRecords.setBusinessType("ec");
            fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getInAccountNo(), fbankPayRecords.getInBankCode()));

            String businessUserId = payAccountService.getBusinessUserIdByAccountId(fbankPayRecords.getOutAccountNo());
            fbankPayRecords.setExtra(getExtraByBusinessUserIdAndPayNo(businessUserId, fbankPayRecords.getClientSerialNo()));
            fbankPayRecords.setRemarks("平安支付-" + "【" + fbankPayRecords.getTradeType() + "】");

        } else if ("支付到平台".equals(fbankPayRecords.getTradeType())) {
            fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo());
            SeparateOrder separateOrder = separateOrderService.querySeparateOrderBySeparateNo(fbankPayRecords.getClientSerialNo());
            if(separateOrder == null) {
                return;
            }
            fbankPayRecords.setBusinessType("ec");
            fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getInAccountNo(), fbankPayRecords.getInBankCode()));

            String businessUserId = payAccountService.getBusinessUserIdByAccountId(separateOrder.getCustMerchant());
            if (!StringUtils.isNumeric(StringUtils.remove(separateOrder.getBusinessOrderNo(), "_"))) {
                fbankPayRecords.setExtra(getExtraByBusinessUserIdAndSeparateOrder(businessUserId, separateOrder));

                if (separateOrder.getBusinessOrderNo().contains("PLATFORM_DISCOUNT_AMOUNT")) {
                    fbankPayRecords.setRemarks("平安结算-退营销");
                } else if (separateOrder.getBusinessOrderNo().contains("BROKERAGE_AMOUNT")) {
                    fbankPayRecords.setRemarks("平安结算-收佣金");
                } else if (fbankPayRecords.getRemarks() != null && fbankPayRecords.getRemarks().contains("购物金")) {
                    fbankPayRecords.setRemarks("平安结算-退购物金");
                } else {
                    RefundOrder refundOrder = refundOrderMapper.selectByBusinessRefundNo2(separateOrder.getBusinessOrderNo());
                    if (refundOrder != null) {
                        fbankPayRecords.setRemarks("平安结算-退购物金");
                    } else {
                        fbankPayRecords.setRemarks("平安结算-未知费项");
                    }
                }

            } else {
                fbankPayRecords.setExtra(getExtraByBusinessUserIdAndRefundNo(businessUserId, separateOrder.getBusinessOrderNo()));
                fbankPayRecords.setRemarks("平安结算-富民退款、富民降级到直连退款");
            }

        } else if (FbankTradeCodeEnum.FTC_24.getDesc().equals(fbankPayRecords.getTradeType())) {
            // 资方交易T0
            fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo());
            fbankPayRecords.setBusinessType("ec");
            fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getOutAccountNo(), fbankPayRecords.getOutBankCode()));
            fbankPayRecords.setRemarks("平安资方账户交易");

            SeparateOrder separateOrder = separateOrderService.querySeparateOrderBySeparateNo(fbankPayRecords.getClientSerialNo());
            if (separateOrder == null) {
                return;
            }

            fbankPayRecords.setExtra(getExtraByXAccountAndSeparateOrder(separateOrder));
        } else if ("直接支付T0".equals(fbankPayRecords.getTradeType())) {
            fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo());
            fbankPayRecords.setBusinessType("ec");
            fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getOutAccountNo(), fbankPayRecords.getOutBankCode()));

            SeparateOrder separateOrder = separateOrderService.querySeparateOrderBySeparateNo(fbankPayRecords.getClientSerialNo());
            if (separateOrder == null) {
                return;
            }

            String businessUserId = payAccountService.getBusinessUserIdByAccountId(separateOrder.getCustNoIn());
            if (separateOrder.getBusinessOrderNo().toLowerCase().contains("manage") ||
                    (fbankPayRecords.getRemarks() != null && fbankPayRecords.getRemarks().contains("manage"))) {
                fbankPayRecords.setExtra(getExtraByBusinessUserIdAndSeparateOrder(businessUserId, separateOrder));
                fbankPayRecords.setRemarks("平安管理后台-转账");

            } else if (separateOrder.getBusinessOrderNo().toLowerCase().contains("credit")) {
                fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getInAccountNo(), fbankPayRecords.getInBankCode()));
                fbankPayRecords.setExtra(getExtraByBusinessUserIdAndSeparateOrder(businessUserId, separateOrder));
                fbankPayRecords.setRemarks("平安信用支付-买家支付到卖家");

            } else if (separateOrder.getBusinessOrderNo().startsWith("G") && StringUtils.isNotBlank(fbankPayRecords.getRemarks())
                    && fbankPayRecords.getRemarks().contains("购物金")) {
                // 购物金充值
                String bizUserId = businessUserId;
                if (fbankPayRecords.getRemarks().contains("平安商户转入购物金")) {
                    bizUserId = payAccountService.getBusinessUserIdByAccountId(separateOrder.getCustMerchant());
                }
                String extra = bizUserId + "|" + separateOrder.getBusinessOrderNo() + "|" + fbankPayRecords.getRemarks();
                fbankPayRecords.setExtra(extra);
            } else if (!StringUtils.isNumeric(separateOrder.getBusinessOrderNo())) {
                fbankPayRecords.setExtra(getExtraByBusinessUserIdAndSeparateOrder(businessUserId, separateOrder));
                if (separateOrder.getBusinessOrderNo().contains("PLATFORM_DISCOUNT_AMOUNT")) {
                    fbankPayRecords.setRemarks("平安结算-付营销");
                } else if (separateOrder.getBusinessOrderNo().contains("BROKERAGE_AMOUNT")) {
                    fbankPayRecords.setRemarks("平安结算-退佣金");
                } else if (fbankPayRecords.getRemarks() != null && fbankPayRecords.getRemarks().contains("购物金")) {
                    fbankPayRecords.setRemarks("平安结算-付购物金");
                } else if (fbankPayRecords.getBusinessOrderType().contains("购物金")) {
                    fbankPayRecords.setRemarks("平安结算-付购物金");
                } else {
                    fbankPayRecords.setRemarks("平安结算-未知费项");
                }
            } else {
                fbankPayRecords.setExtra(getExtraByBusinessUserIdAndRefundNo(businessUserId, separateOrder.getBusinessOrderNo()));
                if (fbankPayRecords.getRemarks() != null && fbankPayRecords.getRemarks().contains("退款时-退款垫资")) {
                    fbankPayRecords.setRemarks("平安退款-结算后实时退款垫资（直接退给买家）");
                } else if (fbankPayRecords.getRemarks() != null && fbankPayRecords.getRemarks().contains("结算时-退款垫资")) {
                    fbankPayRecords.setRemarks("平安结算-结算时退款垫资（卖家分账补齐）");
                } else {
                    fbankPayRecords.setRemarks("平安退款时垫资、或结算时垫资（无法区分）");
                }
            }

        } else if ("会员间交易退款".equals(fbankPayRecords.getTradeType())) {
            SeparateOrder separateOrder = separateOrderService.querySeparateOrderByRefundSettleNo(fbankPayRecords.getOrderNo());
            if(separateOrder == null) {
                separateOrder = separateOrderService.querySeparateOrderBySeparateNo(fbankPayRecords.getClientSerialNo());
                if(separateOrder == null) {
                    log.error("{} 会员间交易退款##separateOrder数据不存在, clientSerialNo:{}", logPrefix, fbankPayRecords.getClientSerialNo());
                    return;
                }
            }

            fbankPayRecords.setBusinessNo(separateOrder.getSeparateNo());
            fbankPayRecords.setBusinessType("ec");
            fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getInAccountNo(), fbankPayRecords.getInBankCode()));

            String businessUserId = payAccountService.getBusinessUserIdByAccountId(separateOrder.getCustNoIn());
            if (separateOrder.getBusinessOrderNo().toLowerCase().contains("credit")) {
                // 信用支付卖家退给买家
                fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getOutAccountNo(), fbankPayRecords.getOutBankCode()));
                fbankPayRecords.setExtra(getExtraByBusinessUserIdAndSeparateOrder(businessUserId, separateOrder));
                fbankPayRecords.setRemarks("平安信用支付-卖家退款给买家");
            } else {
                // 非信用支付退款垫资回补
                fbankPayRecords.setExtra(getExtraByBusinessUserIdAndRefundNo(businessUserId, separateOrder.getBusinessOrderNo()));
                fbankPayRecords.setRemarks("平安结算-平安退款、平安降级到直连退款");
            }

        } else if ("冻结充值".equals(fbankPayRecords.getTradeType())
                || "见证+收单充值".equals(fbankPayRecords.getTradeType())) {
            fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo());
            fbankPayRecords.setBusinessType("ec");
            fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getInAccountNo(), fbankPayRecords.getInBankCode()));

            String businessUserId = payAccountService.getBusinessUserIdByAccountId2(fbankPayRecords.getInAccountNo());
            if (StringUtils.isBlank(businessUserId)) {
                businessUserId = fbankPayRecords.getInAccountNo();
            }
            fbankPayRecords.setExtra(getExtraByBusinessUserIdAndPayNo(businessUserId, fbankPayRecords.getClientSerialNo()));
            fbankPayRecords.setRemarks("平安支付-" + "【" + fbankPayRecords.getTradeType() + "】");

        } else if ("冻结充值撤销".equals(fbankPayRecords.getTradeType())) {
            fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo());
            refreshFbankRechargeRefundBizNo(fbankPayRecords, logPrefix);
            fbankPayRecords.setBusinessType("ec");
            fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getOutAccountNo(), fbankPayRecords.getOutBankCode()));

            String businessUserId = payAccountService.getBusinessUserIdByAccountId(fbankPayRecords.getOutAccountNo());
            fbankPayRecords.setExtra(getExtraByBusinessUserIdAndPayNo(businessUserId, fbankPayRecords.getClientSerialNo()));
            fbankPayRecords.setRemarks("平安退款-" + "【" + fbankPayRecords.getTradeType() + "】");

        } else if ("见证+收单退款".equals(fbankPayRecords.getTradeType())) {
            // fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo());
            fbankPayRecords.setBusinessType("ec");
            fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getOutAccountNo(), fbankPayRecords.getOutBankCode()));

            String businessUserId = payAccountService.getBusinessUserIdByAccountId2(fbankPayRecords.getOutAccountNo());
            if (StringUtils.isBlank(businessUserId)) {
                businessUserId = fbankPayRecords.getOutAccountNo();
            }

            // key: businessNo  value: extra
            PairDto<String, String> businessNoAndExtra = getBusinessNoAndExtra(businessUserId, fbankPayRecords.getClientSerialNo());
            fbankPayRecords.setBusinessNo(businessNoAndExtra.getKey());
            fbankPayRecords.setExtra(businessNoAndExtra.getValue());

            // fbankPayRecords.setExtra(getExtraByBusinessUserIdAndRefundNo(businessUserId, fbankPayRecords.getClientSerialNo()));
            fbankPayRecords.setRemarks("平安退款-" + "【" + fbankPayRecords.getTradeType() + "】");

        } else if ("分账".equals(fbankPayRecords.getTradeType())
                || "分账撤销".equals(fbankPayRecords.getTradeType())) {
            SeparateOrder separateOrder = separateOrderService.querySeparateOrderBySeparateNo(fbankPayRecords.getClientSerialNo());
            if(separateOrder == null) {
                log.error("{} 分账或分账撤销##separateOrder正向数据不存在, clientSerialNo:{}", logPrefix, fbankPayRecords.getClientSerialNo());
                return;
            }

            // 填充businessNo
            fillFbankBizNoWithFZCatchEx(logPrefix, fbankPayRecords, separateOrder);

            fbankPayRecords.setBusinessType("ec");
            fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getInAccountNo(), fbankPayRecords.getInBankCode()));

            String businessUserId = payAccountService.getBusinessUserIdByAccountId(separateOrder.getCustNoIn());
            fbankPayRecords.setExtra(getExtraByBusinessUserIdAndPayNo(businessUserId, separateOrder.getBusinessOrderNo()));
            if ("分账".equals(fbankPayRecords.getTradeType())) {
                fbankPayRecords.setRemarks("平安结算-平安支付、平安降级到直连支付");
            } else if ("分账撤销".equals(fbankPayRecords.getTradeType())) {
                fbankPayRecords.setRemarks("平安退款-平安退款、平安降级到直连退款（结算后实时退款）");
            }

        } else {
            log.info("PinganTradeCheckBillService#fillParams other tradeType, fbankPayRecords={}", JSON.toJSONString(fbankPayRecords));

            // 填充其他交易类型businessNo
            fillFbankBizNoWithOtherTradeTypeCatchEx(logPrefix, fbankPayRecords);

            fbankPayRecords.setBusinessType("ec");

            if (StringUtils.isBlank(fbankPayRecords.getOutBankCode()) && StringUtils.isNotBlank(fbankPayRecords.getInBankCode())) {
                fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getInAccountNo(), fbankPayRecords.getInBankCode()));

            } else if (StringUtils.isNotBlank(fbankPayRecords.getOutBankCode()) && StringUtils.isBlank(fbankPayRecords.getInBankCode())) {
                fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getOutAccountNo(), fbankPayRecords.getOutBankCode()));

            } else if (StringUtils.isNotBlank(fbankPayRecords.getOutBankCode()) && StringUtils.isNotBlank(fbankPayRecords.getInBankCode())
                    && fbankPayRecords.getOutBankCode().equals(fbankPayRecords.getInBankCode())) {
                fbankPayRecords.setBusinessOrderType(getAccountTypeByUserInfo(fbankPayRecords.getInAccountNo(), fbankPayRecords.getInBankCode()));

            } else {
                fbankPayRecords.setBusinessOrderType("平安未知子账户或【平安内部】未知子账户");
            }

            fbankPayRecords.setExtra("||||");
        }
    }

    private String getExtraByXAccountAndSeparateOrder(SeparateOrder separateOrder) {
        String extra = separateOrder.getSeparateNo() + "|" + separateOrder.getSettleNo() + "|" + separateOrder.getBizNo() + "|";

        if (StringUtils.isNotBlank(separateOrder.getBizNo())) {
            PrePayOrder prePayOrder = prePayOrderMapper.getPrePayOrderByPayNo(separateOrder.getBizNo());
            if (prePayOrder != null) {
                extra += prePayOrder.getBusinessOrderNo();
            }
        }

        extra += "|";
        return extra;
    }

    private PairDto<String, String> getBusinessNoAndExtra(String businessUserId, String clientSerialNo) {
        String businessNo = clientSerialNo;
        String extra = businessUserId + "|";

        final String targetRefundNo = getTargetRefundNo(clientSerialNo);
        final String targetChannelRefundNo = getTargetChannelRefundNo(clientSerialNo);

        RefundOrder refundOrder = refundOrderMapper.selectByRefundNo(targetRefundNo);
        if (refundOrder == null) {
            refundOrder = refundOrderMapper.selectByChannelRefundNo2(targetChannelRefundNo);
        }

        if (refundOrder != null) {
            businessNo = refundOrder.getRefundNo();
            extra += refundOrder.getBusinessRefundNo() + "|" + refundOrder.getRefundNo() + "|" + getChannelInfoByRefundOrder(refundOrder);
        } else {
            extra += "未知业务侧退款单号" + "|" + clientSerialNo  + "|" + getChannelInfoByRefundOrder(refundOrder);
        }

        return new PairDto<String, String>().setKey(businessNo).setValue(extra);
    }

    private String getTargetChannelRefundNo(String clientSerialNo) {
        if (clientSerialNo.startsWith("T")) {
            return clientSerialNo;
        }
        return "T" + clientSerialNo;
    }

    private String getTargetRefundNo(String clientSerialNo) {
        if (!clientSerialNo.startsWith("T")) {
            return clientSerialNo;
        }
        return clientSerialNo.substring(1);
    }

    private void fillFbankBizNoWithOtherTradeTypeCatchEx(String logPrefix, FbankPayRecords fbankPayRecords) {
        try {
            fillFbankBizNoWithOtherTradeType(logPrefix, fbankPayRecords);
            afterFillFbankBizNoProcess(logPrefix, fbankPayRecords);
        } catch (Exception e) {
            log.error("{} fbankPayRecords fillFbankBizNoWithOtherTradeType 其他交易类型填充businessNo错误, clientSerialNo:{}", logPrefix, fbankPayRecords.getClientSerialNo(), e);
        }
    }

    private void afterFillFbankBizNoProcess(String logPrefix, FbankPayRecords fbankPayRecords) {
        if (Objects.equals(fbankPayRecords.getTradeCode(), FbankTradeCodeEnum.FTC_7_1.getCode())) {
            // 平安贷退款场景 确认fbank.business_no == refund_order.refund_no
            String businessNo = fbankPayRecords.getBusinessNo();

            final String targetRefundNo = getTargetRefundNo(businessNo);
            final String targetChannelRefundNo = getTargetChannelRefundNo(businessNo);

            RefundOrder refundOrder = refundOrderMapper.selectByRefundNo(targetRefundNo);
            if (refundOrder == null) {
                refundOrder = refundOrderMapper.selectByChannelRefundNo2(targetChannelRefundNo);
            }

            if (refundOrder != null) {
                fbankPayRecords.setBusinessNo(refundOrder.getRefundNo());
            }
        }
    }

    private void fillFbankBizNoWithOtherTradeType(String logPrefix, FbankPayRecords fbankPayRecords) {
        String tradeType = fbankPayRecords.getTradeType();
        String clientSerialNo = fbankPayRecords.getClientSerialNo();
        // 提现
        if (Objects.equals(tradeType, FbankTradeCodeEnum.FTC_15.getDesc())) {
            // client_serial_no = withdraw_order.withdraw_no
            fbankPayRecords.setBusinessNo(clientSerialNo);
        }

        // 可提现冻结 会员冻结
        else if (Objects.equals(tradeType, FbankTradeCodeEnum.FTC_20.getDesc()) || Objects.equals(tradeType, FbankTradeCodeEnum.FTC_2.getDesc())) {
            // 1878640974461161526|1878397876602687525_CREDIT_FREEZE|
            // char_length(client_serial_no) > 19
            String[] s = clientSerialNo.split("\\|");
            if (s.length >= 1) {
                fbankPayRecords.setBusinessNo(s[0]);
            } else {
                log.error("{} 可提现冻结##clientSerialNo格式不正确, clientSerialNo:{}", logPrefix, clientSerialNo);
            }
        }

        // 可提现解冻 会员解冻
        else if (Objects.equals(tradeType, FbankTradeCodeEnum.FTC_12.getDesc()) || Objects.equals(tradeType, FbankTradeCodeEnum.FTC_21.getDesc())) {
            // 1878663858978045986|1877620524029333548_CREDIT_FREEZE|
            String[] s = fbankPayRecords.getClientSerialNo().split("\\|");
            if (s.length >= 1) {
                fbankPayRecords.setBusinessNo(s[0]);
            } else {
                log.error("{} 可提现解冻##clientSerialNo格式不正确, clientSerialNo:{}", logPrefix, fbankPayRecords.getClientSerialNo());
            }
        }

        // 在途充值 在途充值撤销
        else if (Objects.equals(tradeType, FbankTradeCodeEnum.FTC_4.getDesc()) || Objects.equals(tradeType, FbankTradeCodeEnum.FTC_3.getDesc())) {
            // 	2406027048437250|1796936953292148779|
            String[] s = fbankPayRecords.getClientSerialNo().split("\\|");
            if (s.length > 1) {
                fbankPayRecords.setBusinessNo(s[1]);
            } else {
                fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo());
            }

            if (Objects.equals(tradeType, FbankTradeCodeEnum.FTC_3.getDesc())) {
                refreshFbankRechargeRefundBizNo(fbankPayRecords, logPrefix);
            }

        }

        // 见证+收单退款撤销
        else if (Objects.equals(tradeType, FbankTradeCodeEnum.FTC_22.getDesc())) {
            // 1875094848101437486#FC|T1875094848101437486|
            // fixme 场景待确认
            if (fbankPayRecords.getClientSerialNo().contains("#FC")) {
                fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo().substring(0, fbankPayRecords.getClientSerialNo().indexOf("#FC")));
            }
        }

        // 见证+收单资金流水
        else if (Objects.equals(tradeType, FbankTradeCodeEnum.FTC_7.getDesc())) {
            // 平安贷支付
            // 一笔平安贷支付单，fbank_pay_records应该包含两条记录：1852149964625428547
            // 见证+收单资金流水：卖家——>买家
            // 见证+收单资金冻结：买家——>平台担保户

            // 2406021410186621251000068_1_1|1797148674522890334|
            // T1879092119784673359
            // 1794601610945511438_1|T1794601610945511438|
            String[] s = fbankPayRecords.getClientSerialNo().split("\\|");
            if (s.length > 1) {
                String targetBusinessNo = s[1];
                if (targetBusinessNo.startsWith("T")) { // 带T为平安支付渠道重复发起退款时拼接的前缀
                    fbankPayRecords.setTradeCode(FbankTradeCodeEnum.FTC_7_1.getCode());
                    targetBusinessNo = targetBusinessNo.substring(1);
                }
                fbankPayRecords.setBusinessNo(targetBusinessNo);
            } else if (fbankPayRecords.getClientSerialNo().startsWith("T")) {
                fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo().substring(1));
                fbankPayRecords.setTradeCode(FbankTradeCodeEnum.FTC_7_1.getCode());
            } else {
                fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo());
            }
        }

    }

    private void fillFbankBizNoWithFZCatchEx(String logPrefix, FbankPayRecords fbankPayRecords, SeparateOrder separateOrder) {
        try {
            fillFbankBizNoWithFZ(logPrefix, fbankPayRecords, separateOrder);
        } catch (Exception e) {
            log.error("{} fbankPayRecords fillFbankBizNoWithFZ 分账撤销填充businessNo错误, clientSerialNo:{}", logPrefix, fbankPayRecords.getClientSerialNo(), e);
        }
    }

    private void fillFbankBizNoWithFZ(String logPrefix, FbankPayRecords fbankPayRecords, SeparateOrder separateOrder) {
        // 分账
        if (Objects.equals(FbankTradeCodeEnum.FTC_13.getDesc(), fbankPayRecords.getTradeType())) {
            fbankPayRecords.setBusinessNo(fbankPayRecords.getClientSerialNo());
        }
        // 分账撤销
        else if (Objects.equals(FbankTradeCodeEnum.FTC_14.getDesc(), fbankPayRecords.getTradeType())) {
            String fBusinessNo = getFBusinessNoWithFTC_14(logPrefix, fbankPayRecords, separateOrder);
            if (StringUtils.isNotBlank(fBusinessNo)) {
                fbankPayRecords.setBusinessNo(fBusinessNo);
            }
        }
    }

    private String getFBusinessNoWithFTC_14(String logPrefix, FbankPayRecords fbankPayRecords, SeparateOrder separateOrder) {
        String fBusinessNo = null;
        String businessOrderNo = separateOrder.getBusinessOrderNo();
        // 一个支付单存在多次部分退款的情况，比对退款金额匹配记录
        List<RefundOrder> refundOrders = refundOrderMapper.selectListByPayNo(businessOrderNo);
        if (CollectionUtils.isEmpty(refundOrders)) {
            log.error("{} 分账撤销##退款单不存在, clientSerialNo:{}, businessOrderNo:{}", logPrefix, fbankPayRecords.getClientSerialNo(), businessOrderNo);
            return fBusinessNo;
        }

        List<String> querySeparateOrderBisNos = new ArrayList<>();
        for (RefundOrder refundOrder : refundOrders) {
            if (Objects.equals(refundOrder.getChannel(), "pingan_credit")) {
                querySeparateOrderBisNos.add(refundOrder.getRefundNo() + "_" + "PINGAN_CREDIT_PAY_SETTLE_REFUND");
            } else {
                querySeparateOrderBisNos.add(refundOrder.getRefundNo() + "_" + "PINGAN_SETTLE_REFUND");
            }
        }
        List<SeparateOrder> separateOrders = separateOrderService.selectListByBusinessOrderNos(querySeparateOrderBisNos);
        if (CollectionUtils.isEmpty(separateOrders)) {
            log.error("{} 分账撤销##separateOrder逆向数据不存在，clientSerialNo:{}, businessOrderNos:{}", logPrefix, fbankPayRecords.getClientSerialNo(), JSON.toJSONString(querySeparateOrderBisNos));
            return fBusinessNo;
        }

        // 比对退款金额匹配记录
        String fAmount = fbankPayRecords.getAmount();
        BigDecimal fAmountInFen = new BigDecimal(fAmount).abs().multiply(new BigDecimal("100"));
        List<SeparateOrder> confirmSeparateOrderList = separateOrders.stream()
                .filter(s -> s.getTradeAmount() == fAmountInFen.intValue())
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(confirmSeparateOrderList)) {
            log.error("{} 分账撤销##separateOrder逆向数据无金额匹配记录，clientSerialNo:{}", logPrefix, fbankPayRecords.getClientSerialNo());
            return fBusinessNo;
        }
        for (SeparateOrder confirmSeparateOrder : confirmSeparateOrderList) {
            String separateNo = confirmSeparateOrder.getSeparateNo();
            FbankPayRecords existFbankPayRecords = fbankPayRecordsService.queryByBusinessNo(separateNo);
            if (existFbankPayRecords == null) {
                fBusinessNo = separateNo;
                break;
            }
        }
        if (StringUtils.isBlank(fBusinessNo)) {
            log.error("{} 分账撤销##fbankPayRecords-businessNo无匹配记录，clientSerialNo:{}", logPrefix, fbankPayRecords.getClientSerialNo());
            return fBusinessNo;
        }
        return fBusinessNo;
    }

    /**
     * 交易对账
     * 下载平安每日清算文件
     */
    public void downloadDailyClearDataByFileDate(Date fileDate, boolean reimport) {
        try {
            Date toDate = fileDate;
            Date fromDate = DateUtil.getOffsetDay(toDate, -10);
            Date date = fromDate;
            while (true) {
                if (date.after(toDate)) {
                    break;
                }
                downloadDailyClearDataByTradeDate(date, reimport);
                date = DateUtil.getNextDay(date);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 交易对账
     * 下载平安每日清算文件
     */
    public void downloadDailyClearDataByTradeDate(Date tradeDate, boolean reimport) {
        try {
            QueryChannelClearDto queryChannelClearDto = QueryChannelClearDto.builder().
                    startDate(DateUtil.format(tradeDate, DateUtil.DATEFORMATDAY_2)).
                    endDate(DateUtil.format(tradeDate, DateUtil.DATEFORMATDAY_2)).
                    build();
            log.info("PinganTradeCheckBillService#downloadDailyClearData queryChannelClearDto={}", JSON.toJSONString(queryChannelClearDto));
            QueryChannelClearVo queryChannelClearVo = queryChannelClear(queryChannelClearDto);
            if (queryChannelClearVo == null || queryChannelClearVo.getItems() == null) {
                log.info("PinganTradeCheckBillService#downloadDailyClearData queryChannelClearVo is null");
                return;
            }
            for (QueryChannelClearVo.QueryChannelClearItem item : queryChannelClearVo.getItems()) {
                try {
                    log.info("PinganTradeCheckBillService#downloadDailyClearData item={}", JSON.toJSONString(item));

                    if (!"0".equals(item.getClearStatus())) {
                        log.info("PinganTradeCheckBillService#downloadDailyClearData clearStatus not success");
                        continue;
                    }

                    FbankPayRecords recordsVo = new FbankPayRecords();
                    recordsVo.setClientSerialNo("PINGAN_DAILY_CLEAR|" + DateUtil.format(tradeDate, DateUtil.DATEFORMATDAY_2));
                    recordsVo.setMerchantNo("");
                    recordsVo.setOrderNo("PINGAN_DAILY_CLEAR|" + DateUtil.format(tradeDate, DateUtil.DATEFORMATDAY_2));
                    recordsVo.setAmount(AmountCastUtil.castFen2Yuan(item.getTotalAmount()));
                    recordsVo.setTradeStatus("成功");
                    recordsVo.setTradeType("平安挂账子账户清算给商户");
                    recordsVo.setTradeCode(FbankTradeCodeEnum.FTC_23.getCode());
                    recordsVo.setOutAccountName("挂账子账户");
                    recordsVo.setOutAccountNo("挂账子账户");
                    recordsVo.setOutBankCode("挂账子账户");
                    recordsVo.setInAccountName("商户");
                    recordsVo.setInAccountNo("商户");
                    recordsVo.setInBankCode("商户");
                    recordsVo.setBusinessType("ec");
                    recordsVo.setBusinessOrderType("平安挂账子账户（池子）");
                    recordsVo.setExtra("");
                    recordsVo.setRemarks("平安挂账子账户清算给商户|交易归属日期=" + DateUtil.format(tradeDate, DateUtil.DATEFORMATDAY_2));

                    try {
                        recordsVo.setPayTime(ConcurrentSimpleDateFormatUtil.parse(item.getClearFinishDate(), DateUtil.DATEFORMATDAY_2));
                        recordsVo.setSuccessTime(ConcurrentSimpleDateFormatUtil.parse(item.getClearFinishDate(), DateUtil.DATEFORMATDAY_2));
                    } catch (Exception ex) {
                        log.error(ex.getMessage(), ex);
                    }
                    recordsVo.setFeeAmount("0");

                    // 校验交易记录是否存在
                    FbankPayRecords old = fbankPayRecordsService.queryByOrderNo(recordsVo.getOrderNo());
                    if (old != null) {
                        if (reimport) {
                            fbankPayRecordsService.deleteById(old.getId());
                        } else {
                            log.info("PinganTradeCheckBillService#read fbankPayRecordsService.insertSelective is exist, recordsVo.getOrderNo={}", recordsVo.getOrderNo());
                            continue;
                        }
                    }

                    fbankPayRecordsService.insertSelective(recordsVo);

                    log.info("PinganTradeCheckBillService#downloadDailyClearData fbankPayRecordsService.insertSelective success reimport={}", reimport);
                } catch (Exception e0) {
                    log.error("PinganTradeCheckBillService#downloadDailyClearData item error", e0);
                }
            }

        } catch (Exception e) {
            log.error("PinganTradeCheckBillService#downloadDailyClearData error", e);
        }
    }

    /**
     * 账户对账
     *
     * 第一步：解析文件
     * 解析平安对账文件，对账文件类型为YE（余额）的数据
     * 序号&交易网会员代码&子账户&可用金额&
     *
     * 第二步：保存数据库账户对账表
     */
    private void parseAndSavePayReportByPinganCheckBillYE(Date date, String row) {
        try {
            if (StringUtils.isBlank(row)) {
                return;
            }

            String[] rowDatas = row.split("&", -1);
            String payAccountId = rowDatas[1];
            String thirdUserId = rowDatas[2];
            String availableAmount = rowDatas[3];

            String accountName = "";
            UserInfo userInfo = userInfoService.queryUserInfoByAccountId(payAccountId);
            if (userInfo != null) {
                String basicInfoJson = AESUtil.decrypt(userInfo.getBasicInfo(), ApolloUtil.getProperty(ApolloConstants.USER_INFO_KEY));
                accountName = (String) JSONUtils.getValueFromJson(basicInfoJson, "name");
            }

            String businessType = "ec";
            String accountType = getAccountTypeByUserInfo(payAccountId, thirdUserId);

            PayReport payReport = new PayReport();
            payReport.setAccountName(accountName);
            payReport.setAccountType(accountType);
            payReport.setBusinessType(businessType);
            payReport.setPayNo("PINGAN_DAILY_END_AMOUNT|" + DateUtil.format(date, DateUtil.DATEFORMATDAY_2) + "|" + thirdUserId);
            payReport.setCostType("IN");
            payReport.setDealTime(date);
            payReport.setAmount(new BigDecimal(availableAmount));
            payReport.setResidual(new BigDecimal(availableAmount));
            payReport.setOppositeAccount(thirdUserId);
            payReport.setOppositeName(accountName);

            PayAccount payAccount = payAccountService.getPayAccountByAccountId(payAccountId);
            if (payAccount != null) {
                payReport.setAccountIn(payAccount.getBusinessUserId());
            } else {
                payReport.setAccountIn("");
            }

            payReport.setAccountInName(accountName);
            payReport.setCreateTime(new Date());
            payReport.setUpdateTime(new Date());
            payReport.setRemark("平安日终余额");
            payReportService.insert(payReport);

            log.info("PinganTradeCheckBillService#parseAndSavePayReportByPinganCheckBillYE save payReport success={}", JSON.toJSONString(payReport));
        } catch (Exception e) {
            log.error("PinganTradeCheckBillService#parseAndSavePayReportByPinganCheckBillYE error row=" + row, e);
            alarmService.alarm(DingTalkMessage.builder()
                    .type("平安对账账户余额数据解析异常")
                    .method("parseAndSavePayReportByPinganCheckBillYE")
                    .msg("错误信息：" + e.getMessage())
                    .param("row: " + JSON.toJSONString(row))
                    .build());
        }
    }

    public String getAccountTypeByUserInfo(String payAccountId, String thirdUserId) {
        return getAccountTypeByUserInfo(payAccountId, thirdUserId, false);
    }

    public String getAccountTypeByUserInfo(String payAccountId, String thirdUserId, boolean ec_self_split) {
        PayAccount payAccount = null;
        if (StringUtils.isNotBlank(payAccountId)) {
            payAccount = payAccountService.getPayAccountByAccountId(payAccountId);
        }
        String accountType = "";
        if (payAccount != null) {
            if ("ec_pingan_refund".equals(payAccount.getBusinessUserId())) {
                accountType = "平安退款垫资及回补子账户";
            } else if ("ec_pingan_balance".equals(payAccount.getBusinessUserId())) {
                accountType = "平安购物金子账户";
            } else if ("ec_pingan_market".equals(payAccount.getBusinessUserId())) {
                accountType = "平安营销子账户";
            } else if ("ec_pingan_out_brokerage".equals(payAccount.getBusinessUserId())) {
                accountType = "平安退佣金子账户";
            } else if ("ec_pingan_summary".equals(payAccount.getBusinessUserId())) {
                accountType = "平安挂账子账户（池子）";
            } else if ("ec_pingan_in_brokerage".equals(payAccount.getBusinessUserId())) {
                accountType = "平安收佣金、退营销、退购物金、富民退款垫资回补子账户";
            } else if ("ec_pop_big_shop_pingan".equals(payAccount.getBusinessUserId())) {
                accountType = "平安POP未开基本户大商户";
            } else if ("ec_pingan_platform_aggr_withdraw".equals(payAccount.getBusinessUserId())) {
                accountType = "平安聚合提现子账户";
            } else if ("xyd_credit_support".equals(payAccount.getBusinessUserId())) {
                accountType = "平安小雨点资方子账户";
            } else if (payAccount.getBusinessIdType().contains("ec_self_support")) {
                if (ec_self_split) {
                    accountType = payAccount.getBusinessUserId();
                    UserInfo userInfo = userInfoService.queryUserInfoByAccountId(payAccountId);
                    if (userInfo != null) {
                        try {
                            String basicInfoJson = AESUtil.decrypt(userInfo.getBasicInfo(), ApolloUtil.getProperty(ApolloConstants.USER_INFO_KEY));
                            accountType = (String) JSONUtils.getValueFromJson(basicInfoJson, "name");
                        } catch (Exception e) {
                            log.error(e.getMessage(), e);
                        }
                    }
                } else {
                    accountType = "平安自营店铺子账户";
                }
            } else if (payAccount.getBusinessIdType().contains("ec_pop")) {
                accountType = "平安POP商业子账户";
            } else if (payAccount.getBusinessIdType().contains("ec")) {
                accountType = "平安买家子账户";
            } else {
                accountType = "平安未知子账户（payAccountID=" + payAccount.getAccountId() + "）";
            }
        } else {
            if ("4294000000000007".equals(thirdUserId)) {
                accountType = "【平安内部】平台在途子账户";
            } else if ("4294000000000004".equals(thirdUserId)) {
                accountType = "【平安内部】利息子账户";
            } else if ("4294000000000005".equals(thirdUserId)) {
                accountType = "【平安内部】平台担保子账户";
            } else if ("4294000000000021".equals(thirdUserId)) {
                accountType = "【平安内部】退款垫款子账户";
            } else if ("4294000000000013".equals(thirdUserId)) {
                accountType = "【平安内部】营销子账户";
            } else if ("4294000000000107".equals(thirdUserId)) {
                accountType = "【平安内部】云收款T1专属子账户";
            } else {
                accountType = "【平安内部】未知子账户";
            }
        }
        return accountType;
    }

    public String getBusinessUserIdByAccountType(String accountType) {
        String businessUserId = "";
        if (StringUtils.isBlank(accountType)) {
            return businessUserId;
        }

        if ("平安退款垫资及回补子账户".equals(accountType)) {
            businessUserId = "ec_pingan_refund";
        } else if ("平安购物金子账户".equals(accountType)) {
            businessUserId = "ec_pingan_balance";
        } else if ("平安营销子账户".equals(accountType)) {
            businessUserId = "ec_pingan_market";
        } else if ("平安退佣金子账户".equals(accountType)) {
            businessUserId = "ec_pingan_out_brokerage";
        } else if ("平安挂账子账户（池子）".equals(accountType)) {
            businessUserId = "ec_pingan_summary";
        } else if ("平安收佣金、退营销、退购物金、富民退款垫资回补子账户".equals(accountType)) {
            businessUserId = "ec_pingan_in_brokerage";
        } else if ("平安POP未开基本户大商户".equals(accountType)) {
            businessUserId = "ec_pop_big_shop_pingan";
        } else if ("平安聚合提现子账户".equals(accountType)) {
            businessUserId = "ec_pingan_platform_aggr_withdraw";
        }

        return businessUserId;
    }

    private String getExtraByBusinessUserIdAndPayNo(String businessUserId, String payNo) {
        String extra = businessUserId + "|";
        List<PayResultOrder> payResultOrders = payResultOrderService.selectListByPayNo(payNo);
        if (!CollectionUtils.isEmpty(payResultOrders)) {
            PrePayOrder prePayOrder = prePayOrderMapper.getPrePayOrderByPayNo(payNo);
            extra += prePayOrder.getBusinessOrderNo() + "|" + payNo + "|" + getChannelInfoByPayResultList(payResultOrders) + "|";
        } else {
            extra += "未知业务侧支付单号" + "|" + payNo + "|" + getChannelInfoByPayResultList(payResultOrders) + "|";
        }
        return extra;
    }

    private String getExtraByBusinessUserIdAndRefundNo(String businessUserId, String refundNo) {
        String extra = businessUserId + "|";
        RefundOrder refundOrder = refundOrderMapper.selectByRefundNo(refundNo);
        if (refundOrder != null) {
            extra += refundOrder.getBusinessRefundNo() + "|" + refundNo  + "|" + getChannelInfoByRefundOrder(refundOrder);
        } else {
            extra += "未知业务侧退款单号" + "|" + refundNo  + "|" + getChannelInfoByRefundOrder(refundOrder);
        }
        return extra;
    }

    private String getExtraByBusinessUserIdAndSeparateOrder(String businessUserId, SeparateOrder separateOrder) {
        return businessUserId + "|" + separateOrder.getBusinessOrderNo() + "|" + separateOrder.getSeparateNo() + "|" + separateOrder.getSettleNo() + "|";
    }

    private String getChannelInfoByPayResultList(List<PayResultOrder> payResultOrders) {
        if (payResultOrders == null) {
            return "未知渠道侧支付单号";
        }
        String result = "";
        for (PayResultOrder payResultOrder : payResultOrders) {
            result += payResultOrder.getPayChannel() + "#" + payResultOrder.getChannelChargeNo() + "#";
        }
        return result;
    }

    private String getChannelInfoByRefundOrder(RefundOrder refundOrder) {
        if (refundOrder == null) {
            return "未知渠道侧退款单号";
        }
        String result = refundOrder.getChannel() + "#" + refundOrder.getChannelRefundNo() + "#";
        return result;
    }

    /**
     * 查询银行清算结果
     */
    private QueryChannelClearVo queryChannelClear(QueryChannelClearDto param) throws PaymentException {
        log.info("PinganTradeCheckBillService#queryChannelClear param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        /**
         * 查询类型
         * 1 全部
         * 2 指定时间段
         */
        requestParam.put("FunctionFlag", param.getQueryType());
        requestParam.put("StartDate", param.getStartDate());
        requestParam.put("EndDate", param.getEndDate());
        requestParam.put("PageNum", "1");

        try {
            // 2.发送请求
            PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6108);

            // 3.返回响应
            return QueryChannelClearVo.builder().
                    totalNum(result.getResultString("TotalNum")).
                    resultNum(result.getResultString("ResultNum")).
                    endFlag(result.getResultString("EndFlag")).
                    items(QueryChannelClearVo.parseItems(result.getResultList("TranItemArray"))).
                    build();
        }
        catch(PaymentException ex) {
            /**
             * ERR020 没有找到符合条件的记录
             */
            if("ERR020".equals(ex.getErrCode())) {
                return QueryChannelClearVo.builder().build();
            }

            throw ex;
        }
    }


    /**
     * 平安请求
     */
    private PinganResponse invoke(JSONObject param, PinganMethod method) throws PaymentException {
        try {
            // 1.请求头
            if(!param.containsKey("CnsmrSeqNo")) {
                param.put("CnsmrSeqNo", leafUtil.getSnowflakeId());
            }

            if(!PinganMethod.NOT_REQUIRED.equals(method.getMethodCode())) {
                param.put("TxnCode", method.getMethodCode());
                param.put("TxnTime", DateUtil.format(DateUtil.now(), DateUtil.DATEFORMATSECOND_2) + "000");
                param.put("TxnClientNo", ApolloUtil.getProperty("pingan.clientNo"));
            }

            param.put("MrchCode", ApolloUtil.getProperty("pingan.mrchCode"));

            // 2.请求体
            SdkRequest sdkRequest = new SdkRequest();
            sdkRequest.setInterfaceName("/V1.0/" + method.getMethodName());
            sdkRequest.setBody(param);

            // 3.发送请求
            log.info("PinganAdapter#invoke method:{} request:{}", method.name(), JSONUtils.toJSON(param));
            HttpResult response = apiClient.invoke(sdkRequest);
            log.info("PinganAdapter#invoke method:{}（{}） request:{} response:{}", method.name(), method.getDesc(),
                    JSONUtils.toJSON(param), JSONUtils.toJSON(response != null ? response.getData() : response));

            // 4.通信状态判断
            if(response == null || !response.isSuccess()) {
                throw new PaymentException(ResultCodeEnum.DEFAULT_ERROR);
            }

            // 5.响应
            return PinganResponse.parse(response.getData());
        }
        catch(Exception ex) {
            log.error("PinganAdapter#invoke error, param=" + JSONUtils.toJSON(param) + ", errorMsg=" + ex.getMessage(), ex);

            if(ex instanceof OpenBankSdkException) {
                throw new PaymentException(((OpenBankSdkException) ex).getCode(), ex.getMessage());
            }
            else if(ex instanceof OpenBankSdkRunTimeException) {
                throw new PaymentException(((OpenBankSdkRunTimeException) ex).getCode(), ex.getMessage());
            }
            else if(ex instanceof OpenBankFileNotFoundException) {
                throw new PaymentException(((OpenBankFileNotFoundException) ex).getCode(), ex.getMessage());
            }
            else if(ex instanceof PaymentException) {
                throw ex;
            }

            throw new PaymentException(ResultCodeEnum.DEFAULT_ERROR);
        }
    }

    /**
     * 平安响应结构体
     */
    @Getter
    @Setter
    public static class PinganResponse {
        private final static String PINGAN_SUCCESS_CODE = "000000";
        private final static String PINGAN_PAY_SUCCESS_CODE = "T0000";

        private String code;
        private String message;
        private Map<String, Object> extendData;
        private Map<String, Object> data;
        private Map<String, Object> errors;

        /**
         * 是否成功
         */
        public boolean success() {
            if(StringUtils.isBlank(code)) {
                code = getResultString("TxnReturnCode");
            }

            return PINGAN_SUCCESS_CODE.equals(code) ||
                    PINGAN_PAY_SUCCESS_CODE.equals(code);
        }

        /**
         * 获取结果（String）
         */
        public String getResultString(String key) {
            return data == null ? null : (String) data.get(key);
        }

        /**
         * 获取结果（List）
         */
        public List<Map<String, String>> getResultList(String key) {
            return data == null ? null : (List<Map<String, String>>) data.get(key);
        }

        /**
         * 解析响应
         */
        public static PinganResponse parse(String body) throws PaymentException {
            if(StringUtils.isBlank(body)) {
                throw new PaymentException(ResultCodeEnum.DEFAULT_ERROR);
            }

            JSONObject param = JSON.parseObject(body);
            PinganResponse response = new PinganResponse();
            response.setCode(param.getString("Code"));
            response.setMessage(param.getString("Message"));
            response.setExtendData(JSONUtils.parse2Map(param.getString("ExtendData"), String.class, Object.class));
            response.setData(JSONUtils.parse2Map(param.getString("Data"), String.class, Object.class));

            // 失败直接抛出业务异常
            if(!response.success()) {
                String errorCode = (String) response.extendData.get("TxnReturnCode");
                String errorMessage = (String) response.extendData.get("TxnReturnMsg");

                if(StringUtils.isBlank(errorCode)) {
                    errorCode = response.getResultString("TxnReturnCode");
                    errorMessage = response.getResultString("TxnReturnMsg");
                }

                throw new PaymentException(errorCode, errorMessage);
            }

            return response;
        }
    }

}
